#include "mesh_util.h"
#include <sstream>
#include <fstream>
using namespace std;

mesh cube(void) {
    mesh m;

    m.add_vertex(vertex{-0.5, 0.5, 0.5});
    m.add_vertex(vertex{-0.5, -0.5, 0.5});
    m.add_vertex(vertex{0.5, -0.5, 0.5});
    m.add_vertex(vertex{0.5, 0.5, 0.5});
    
    m.add_vertex(vertex{-0.5, 0.5, -0.5});
    m.add_vertex(vertex{-0.5, -0.5, -0.5});
    m.add_vertex(vertex{0.5, -0.5, -0.5});
    m.add_vertex(vertex{0.5, 0.5, -0.5});
    
    m.add_edge(edge(0, 1)); //top
    m.add_edge(edge(1, 2));
    m.add_edge(edge(2, 3));
    m.add_edge(edge(3, 0));

    m.add_edge(edge(4, 7)); //bot
    m.add_edge(edge(7, 6));
    m.add_edge(edge(6, 5));
    m.add_edge(edge(5, 4));

    m.add_edge(edge(1, 0)); //front
    m.add_edge(edge(0, 4));
    m.add_edge(edge(4, 5));
    m.add_edge(edge(5, 1));

    m.add_edge(edge(3, 2)); //back
    m.add_edge(edge(2, 6));
    m.add_edge(edge(6, 7));
    m.add_edge(edge(7, 3));

    m.add_edge(edge(1, 5));
    m.add_edge(edge(5, 6));
    m.add_edge(edge(6, 2));
    m.add_edge(edge(2, 1));

    m.add_edge(edge(3, 7));
    m.add_edge(edge(7, 4));
    m.add_edge(edge(4, 0));
    m.add_edge(edge(0, 3));


    for (int i = 0; i < 4 * 6; ++i) {
        m.add_face(i);
    }

    //insert the last index as well
    for (int i = 0; i <= 6; ++i) {
        m.add_offset(i * 4);
    }

    for (int i = 0; i < 6; ++i) 
        for (int j = 0; j < 4; ++j) m.add_parent(i);

    m.prepare();
    return m;
}

mesh plane(void) {
    mesh m;

    m.add_vertex(vertex{-1, 0, -1});
    m.add_vertex(vertex{-1, 0,  1});
    m.add_vertex(vertex{ 1, 0,  1});
    m.add_vertex(vertex{ 1, 0, -1});

    m.add_edge(edge(0, 1));
    m.add_edge(edge(1, 2));
    m.add_edge(edge(2, 3));
    m.add_edge(edge(3, 0));

    m.add_face(0);
    m.add_face(1);
    m.add_face(2);
    m.add_face(3);

    m.add_offset(0);
    m.add_offset(4);

    for (int i = 0; i < 4; ++i) m.add_parent(0);

    m.prepare();
    return m;
}

mesh tetra(void) {
    mesh m;

    vertex avg{0, 2.0 * sqrt(2.0/3.0), (sqrt(3.0) - 2.0) / (sqrt(3.0) * 3.0)};
    avg /= 4.0;

    m.add_vertex(vertex{ 0, 0, (float)sqrt(3.0) - 1/(float)sqrt(3.0)} - avg);
    m.add_vertex(vertex{-1, 0, - 1/(float)sqrt(3.0)} - avg);
    m.add_vertex(vertex{ 1, 0, - 1/(float)sqrt(3.0)} - avg);
    m.add_vertex(vertex{ 0, 2 * (float)sqrt(2.0/3.0), 0} - avg);

    m.add_edge(edge(0, 1));
    m.add_edge(edge(1, 2));
    m.add_edge(edge(2, 0));

    m.add_edge(edge(0, 2));
    m.add_edge(edge(2, 3));
    m.add_edge(edge(3, 0));
    
    m.add_edge(edge(0, 3));
    m.add_edge(edge(3, 1));
    m.add_edge(edge(1, 0));

    m.add_edge(edge(3, 2));
    m.add_edge(edge(2, 1));
    m.add_edge(edge(1, 3));

    for (int i = 0; i < 4*3; ++i) m.add_face(i);
    for (int i = 0; i <= 4; ++i) m.add_offset(i*3);
    for (int i = 0; i < 4; ++i) 
        for (int j = 0; j < 3; ++j) m.add_parent(i);

    m.prepare();
    return m;
}

mesh mesh_from_obj(const string& fn) {
    mesh m;

    ifstream obj(fn.c_str());
    int last_face_size = 0;
    int face_start = 0;
    int face_id = 0;
    while (obj.good()) {
        string line, tmp;
        getline(obj, line);
        stringstream ss(line);
        ss >> tmp;
        if (tmp == "v") {
            vertex v;
            ss >> v[0] >> v[1] >> v[2];
            m.add_vertex(v * 0.1f + vertex{.0f,-0.4f,.0f});
        }
        if (tmp == "f") {
            vector<int> face_vertices;
            while (ss.good()) {
                int v_id, uv_id;
                char c;
                ss >> v_id >> c >> uv_id;
                face_vertices.push_back(v_id-1);
            }
            
            m.add_offset(face_start);

            for (size_t i = 0; i < face_vertices.size(); ++i) {
                m.add_edge(edge(face_vertices[i], face_vertices[(i+1)%face_vertices.size()]));
                m.add_face(face_start + i);
                m.add_parent(face_id);
            }
            last_face_size = face_vertices.size();
            face_start += last_face_size;
            face_id++;
        }
    }
    m.add_offset(face_start);

    m.prepare();
    return m;
}

